﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization;
using ServerNodeLib;
using System.Data;

namespace ServerNodeLib.DataAdapter
{
	/// <summary>
	/// Just stores the information in xml files in the program directory
	/// </summary>
	public class XmlDataAdapter : INodeDataAdapter, IBatchDataAdapter, IBatchGroupDataAdapter
	{
		public static string BATCH_DIRECTORY = @"data\batch";
		public static string NODE_DIRECTORY = @"data\node";
		public static string BGROUP_DIRECTORY = @"data\bgroup";

		private System.Xml.Serialization.XmlSerializer batchSerializer;
		private System.Xml.Serialization.XmlSerializer nodeSerializer;
		private System.Xml.Serialization.XmlSerializer bgroupSerializer;
		private String basePath;

		private System.Data.DataTable nodes;
		public System.Data.DataTable Nodes
		{
			get
			{
				return nodes;
			}
		}
		private System.Data.DataTable batches;
		public System.Data.DataTable Batches
		{
			get
			{
				return batches;
			}
		}
		//public System.Data.DataTable BatchGroups;

		public XmlDataAdapter()
		{
			batchSerializer = new System.Xml.Serialization.XmlSerializer(typeof(Batch));
			nodeSerializer = new System.Xml.Serialization.XmlSerializer(typeof(Node));
			bgroupSerializer = new System.Xml.Serialization.XmlSerializer(typeof(BatchGroup));
			basePath = System.IO.Directory.GetCurrentDirectory();
			BATCH_DIRECTORY = System.IO.Path.Combine(basePath, BATCH_DIRECTORY);
			NODE_DIRECTORY = System.IO.Path.Combine(basePath, NODE_DIRECTORY);
			BGROUP_DIRECTORY = System.IO.Path.Combine(basePath, BGROUP_DIRECTORY);
			if (System.IO.Directory.Exists(BATCH_DIRECTORY) == false)
			{
				System.IO.Directory.CreateDirectory(BATCH_DIRECTORY);
			}
			if (System.IO.Directory.Exists(NODE_DIRECTORY) == false)
			{
				System.IO.Directory.CreateDirectory(NODE_DIRECTORY);
			}
			if (System.IO.Directory.Exists(BGROUP_DIRECTORY) == false)
			{
				System.IO.Directory.CreateDirectory(BGROUP_DIRECTORY);
			}
			BuildNodesTable();
			BuildBatchesTable();

			LoadNodesTable();
			LoadBatchesTable();
		}

		private void BuildNodesTable()
		{
			this.nodes = new DataTable("nodes");
			nodes.Columns.Add(NodesTableKeys.NodeName, typeof(String));
			nodes.Columns.Add(NodesTableKeys.BatchId, typeof(Guid));
			nodes.Columns.Add(NodesTableKeys.Status, typeof(String));
			nodes.Columns.Add(NodesTableKeys.LastSeen, typeof(DateTime));
			// Just show the next action request
			nodes.Columns.Add(NodesTableKeys.NextAction, typeof(ActionRequest));
			nodes.Columns.Add(NodesTableKeys.NodeId, typeof(Guid));
		}

		private void LoadNodesTable()
		{
			foreach (String file in System.IO.Directory.GetFiles(NODE_DIRECTORY))
			{
				Node n = GetNode(file);
				if (n != null)
				{
					DataRow newRow = this.nodes.NewRow();
					SaveNodeRow(newRow, n);
					this.nodes.Rows.Add(newRow);
				}
			}
		}

		private void SaveNodeRow(DataRow row, Node n)
		{
			row[NodesTableKeys.NodeName] = n.NodeName;
			row[NodesTableKeys.NodeId] = n.NodeId;
			row[NodesTableKeys.BatchId] = n.BatchId;
			row[NodesTableKeys.LastSeen] = n.LastSeen;
			if (n.ActionRequests.Count > 0)
				row[NodesTableKeys.NextAction] = n.ActionRequests[0];
			else
				row[NodesTableKeys.NextAction] = new ActionRequest(ActionRequestType.NoAction);
			row[NodesTableKeys.Status] = n.ToString();
		}

		private DataRow GetNodeRow(Guid nodeId)
		{
			foreach (DataRow r in this.nodes.Rows)
			{
				if (((Guid)r[NodesTableKeys.NodeId]).Equals(nodeId))
					return r;
			}
			return null;
		}

		private void BuildBatchesTable()
		{
			this.batches = new DataTable("batches");
			batches.Columns.Add(BatchTableKeys.BatchId, typeof(Guid));
			batches.Columns.Add(BatchTableKeys.BatchStatus, typeof(String));
			batches.Columns.Add(BatchTableKeys.BatchDuration, typeof(TimeSpan));
			batches.Columns.Add(BatchTableKeys.BatchGroupId, typeof(Guid));
			batches.Columns.Add(BatchTableKeys.NodeId, typeof(Guid));
			batches.Columns.Add(BatchTableKeys.BatchComments, typeof(String));
		}

		private void LoadBatchesTable()
		{
			foreach (String file in System.IO.Directory.GetFiles(BATCH_DIRECTORY))
			{
				Batch b = GetBatch(file);
				if (b != null)
				{
					DataRow newRow = this.batches.NewRow();
					SaveBatchesRow(newRow, b);
					this.batches.Rows.Add(newRow);
				}
			}
		}

		private void SaveBatchesRow(DataRow row, Batch b)
		{
			row[BatchTableKeys.BatchId] = b.BatchId;
			row[BatchTableKeys.BatchDuration] = DateTime.Now.Subtract(b.NodeBegin);
			row[BatchTableKeys.BatchGroupId] = b.BatchGroup;
			row[BatchTableKeys.BatchStatus] = b.Status.ToString();
			row[BatchTableKeys.BatchComments] = b.Comments;
			row[BatchTableKeys.NodeId] = b.NodeId;
		}

		private DataRow GetBatchesRow(Guid batchId)
		{
			foreach (DataRow r in this.batches.Rows)
			{
				if (((Guid)r[BatchTableKeys.BatchId]).Equals(batchId))
					return r;
			}
			return null;
		}

		#region INodeDataAdapter Members

		private String GetNodeFilename(Guid nodeId)
		{
			return System.IO.Path.Combine(NODE_DIRECTORY, nodeId.ToString() + ".xml");
		}

		private Node GetNode(String filename)
		{
			System.IO.StreamReader reader = null;
			try
			{
				if (System.IO.File.Exists(filename))
				{
					reader = new System.IO.StreamReader(filename);
					Node n = (Node)nodeSerializer.Deserialize(reader);
					reader.Close();
					return n;
				}
				else
				{
					return null;
				}
			}
			catch (Exception)
			{
				return null;
			}
			finally
			{
				if (reader != null)
					reader.Close();
			}
		}

		public Node GetNode(Guid nodeId)
		{
			String filename = GetNodeFilename(nodeId);
			return GetNode(filename);
		}

		public List<Node> GetNodes()
		{
			List<Node> retList = new List<Node>();
			foreach (String file in System.IO.Directory.GetFiles(NODE_DIRECTORY))
			{
				Node n = GetNode(file);
				if (n != null)
					retList.Add(n);
			}
			return retList;
		}

		public List<Node> GetNodes(NodeState state)
		{
			List<Node> retList = new List<Node>();
			foreach (String file in System.IO.Directory.GetFiles(NODE_DIRECTORY))
			{
				Node n = GetNode(file);
				if (n != null && n.State == state)
					retList.Add(n);
			}
			return retList;
		}

		public int NumberNodes(NodeState state)
		{
			// I know how efficient this is
			// if the scale becomes an issue I'll add a look-up table
			return GetNodes(state).Count;
		}

		public int NumberNodes()
		{
			return System.IO.Directory.GetFiles(NODE_DIRECTORY).Length;
		}

		public List<Node> GetNodes(DateTime minStartTime, DateTime maxStartTime)
		{
			throw new NotImplementedException();
		}

		public List<Node> GetNodes(DateTime maxLastSeen)
		{
			List<Node> retList = new List<Node>();
			foreach (String file in System.IO.Directory.GetFiles(NODE_DIRECTORY))
			{
				Node n = GetNode(file);
				if (n != null && n.LastSeen.CompareTo(maxLastSeen) < 0)
					retList.Add(n);
			}
			return retList;
		}

		public bool SaveNode(Node node)
		{
			String filename = GetNodeFilename(node.NodeId);
			System.IO.StreamWriter writer = null;
			try
			{
				writer = new System.IO.StreamWriter(filename);
				nodeSerializer.Serialize(writer, node);
				writer.Close();

				DataRow r = GetNodeRow(node.NodeId);
				if (r == null)
				{
					r = this.nodes.NewRow();
					this.nodes.Rows.Add(r);
				}
				SaveNodeRow(r, node);

				return true;
			}
			catch (Exception)
			{
				return false;
			}
			finally
			{
				if (writer != null)
					writer.Close();
			}
		}

		public bool DeleteNode(Guid nodeId)
		{
			DataRow r = GetNodeRow(nodeId);
			if (r != null)
			{
				this.nodes.Rows.Remove(r);
			}

			Batch b = GetBatchOnNode(nodeId);
			if (b != null)
			{
				ResetBatch(b);
				SaveBatch(b);
			}

			System.IO.File.Delete(GetNodeFilename(nodeId));
			return true;
		}

		public List<ActionRequest> LoadActionRequests(Guid nodeId)
		{
			throw new NotImplementedException();
		}

		#endregion

		#region IBatchDataAdapter Members

		private String GetBatchFilename(Guid batchId)
		{
			return System.IO.Path.Combine(BATCH_DIRECTORY, batchId.ToString() + ".xml");
		}

		private Batch GetBatch(String filename)
		{
			System.IO.StreamReader reader = null;
			try
			{
				if (System.IO.File.Exists(filename))
				{
					reader = new System.IO.StreamReader(filename);
					Batch b = (Batch)batchSerializer.Deserialize(reader);
					reader.Close();
					return b;
				}
				else
				{
					return null;
				}
			}
			catch (Exception)
			{
				return null;
			}
			finally
			{
				if (reader != null)
					reader.Close();
			}
		}

		public Batch GetBatch(Guid batchId)
		{
			String filename = GetBatchFilename(batchId);
			return GetBatch(filename);
		}

		public Batch GetBatchOnNode(Guid nodeId)
		{
			Node n = GetNode(nodeId);
			if (n == null)
			{
				return null;
			}
			else
			{
				// easy way
				Batch easyb = GetBatch(n.BatchId);
				if (easyb != null)
				{
					return easyb;
				}
				else
				{
					// hard way
					foreach (Batch b in GetBatches(BatchStatus.InProgress))
					{
						if (b.NodeId.Equals(nodeId))
							return b;
					}
					return null;
				}
			}
		}

		public List<Batch> GetBatches(BatchStatus status)
		{
			return GetBatches(status, 128);
		}

		public List<Batch> GetBatches(BatchStatus status, int limit)
		{
			List<Batch> retList = new List<Batch>();
			foreach (String file in System.IO.Directory.GetFiles(BATCH_DIRECTORY))
			{
				if (retList.Count == limit)
					break;
				Batch b = GetBatch(file);
				if (b != null && b.Status == status)
					retList.Add(b);
			}
			return retList;
		}

		public int NumberBatches(BatchStatus status)
		{
			return GetBatches(status).Count;
		}

		public int NumberBatches()
		{
			return System.IO.Directory.GetFiles(BATCH_DIRECTORY).Length;
		}

		public List<Batch> GetBatchesInGroup(Guid groupId)
		{
			List<Batch> retList = new List<Batch>();
			foreach (String file in System.IO.Directory.GetFiles(BATCH_DIRECTORY))
			{
				Batch b = GetBatch(file);
				if (b != null && b.BatchGroup == groupId)
					retList.Add(b);
			}
			return retList;
		}

		public bool SaveBatch(Batch batch)
		{
			String filename = GetBatchFilename(batch.BatchId);
			System.IO.StreamWriter writer = null;
			try
			{
				writer = new System.IO.StreamWriter(filename);
				batchSerializer.Serialize(writer, batch);
				writer.Close();

				DataRow r = GetBatchesRow(batch.BatchId);
				if (r == null)
				{
					r = this.batches.NewRow();
					this.batches.Rows.Add(r);
				}
				SaveBatchesRow(r, batch);

				return true;
			}
			catch (Exception)
			{
				return false;
			}
			finally
			{
				if (writer != null)
					writer.Close();
			}
		}

		public bool DeleteBatch(Guid batchId)
		{
			DataRow r = GetBatchesRow(batchId);
			if (r != null)
			{
				this.batches.Rows.Remove(r);
			}

			Batch b = GetBatch(batchId);
			if (b == null)
				return false;
			BatchGroup bg = GetBatchGroup(b.BatchGroup);
			if (bg != null)
			{
				if (b.Status == BatchStatus.NotStarted)
					bg.RemainingBatches--;
				else if (b.Status == BatchStatus.Completed)
					bg.CompleteBatches--;
				bg.TotalBatches--;
				if (bg.TotalBatches <= 0)
				{
					DeleteBatchGroup(bg.GroupId);
				}
				else
				{
					SaveBatchGroup(bg);
				}
			}
			System.IO.File.Delete(GetBatchFilename(batchId));
			return true;
		}

		public BatchResource GetBatchResource(Guid BatchId, string clientName)
		{
			Batch b = GetBatch(BatchId);
			if (b == null)
				return null;
			foreach (BatchResource br in b.BatchResources)
			{
				if (br.ClientPath.Equals(clientName))
					return br;
			}
			return null;
		}

		public List<BatchCommand> LoadBatchCommands(Guid BatchId)
		{
			throw new NotImplementedException();
		}

		public List<BatchResource> LoadBatchResources(Guid BatchId)
		{
			throw new NotImplementedException();
		}

		public List<BatchResource> LoadReturnResources(Guid BatchId)
		{
			throw new NotImplementedException();
		}

		#endregion

		#region IBatchGroupDataAdapter Members

		private String GetBatchGroupFilename(Guid groupId)
		{
			return System.IO.Path.Combine(BGROUP_DIRECTORY, groupId.ToString() + ".xml");
		}
		private BatchGroup GetBatchGroup(String filename)
		{
			System.IO.StreamReader reader = null;
			try
			{
				if (System.IO.File.Exists(filename))
				{
					reader = new System.IO.StreamReader(filename);
					BatchGroup bg = (BatchGroup)bgroupSerializer.Deserialize(reader);
					reader.Close();
					return bg;
				}
				else
				{
					return null;
				}
			}
			catch (Exception)
			{
				return null;
			}
			finally
			{
				if (reader != null)
					reader.Close();
			}
		}

		public BatchGroup GetBatchGroup(Guid GroupId)
		{
			String filename = GetBatchGroupFilename(GroupId);
			return GetBatchGroup(filename);
		}

		public bool SaveBatchGroup(BatchGroup group)
		{
			String filename = GetBatchGroupFilename(group.GroupId);
			System.IO.StreamWriter writer = null;
			try
			{
				writer = new System.IO.StreamWriter(filename);
				bgroupSerializer.Serialize(writer, group);
				writer.Close();
				return true;
			}
			catch (Exception)
			{
				return false;
			}
			finally
			{
				if (writer != null)
					writer.Close();
			}
		}

		public bool DeleteBatchGroup(Guid groupId)
		{
			System.IO.File.Delete(GetBatchGroupFilename(groupId));
			return true;
		}

		#endregion

		/// <summary>
		/// Resets the given batch
		/// </summary>
		/// <param name="b"></param>
		public static void ResetBatch(Batch b)
		{
			if (b == null)
				return;
			b.NodeId = Guid.Empty;
			b.NodeBegin = DateTime.MinValue;
			b.NodeEnd = DateTime.MaxValue;
			b.Status = BatchStatus.NotStarted;
			b.Result = null;
		}
	}
}
